﻿using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;

namespace RemoteObject
{

    [Serializable]
    public class BlockInfo
    {
        public int blockID, originPos, currentPos, remainDataLength;
        public BlockInfo(int _blockID, int _originPos, int _currentPos, int _remainDataLength)
        {
            blockID = _blockID;
            originPos = _originPos;
            currentPos = _currentPos;
            remainDataLength = _remainDataLength;
        }
    }

    [Serializable]
    public class DownloadWorkder : FileTransWorker
    {
        public RemoteObject.RemoteObjectDownloader proxyObjectTcpReuse = null;
        List<Common.BlockBufferInfo> blocksReceived = new List<Common.BlockBufferInfo>();

        public DownloadWorkder(String _fnRemote, String _fnLocal, String _serverUrl, BlockInfo _blockInfo, DownloadJob _job)
        {
            fnRemote = _fnRemote;
            fnLocal = _fnLocal;
            blockInfo = _blockInfo;
            serverUrl = _serverUrl;
            ownerJob = _job;
            ownerJob.fileTransWorkers.Add(this);
        }

        protected override void _FileAccessProc()
        {
            do
            {
                Thread.Sleep(10);
                if (blocksReceived.Count == 0)
                    continue;

                List<Common.BlockBufferInfo> blocksReceivedToSave;
                lock (this)
                {
                    blocksReceivedToSave = blocksReceived;
                    blocksReceived = new List<Common.BlockBufferInfo>();
                }

                if (blocksReceivedToSave.Count != 0)
                {
                    foreach (Common.BlockBufferInfo bbi in blocksReceivedToSave)
                    {
                        if (bbi == Common.endingBlockBuffer)
                        {
                            return;
                        }
                        else
                        {
                            if (!accessFileError)
                            {
                                try
                                {
                                    if (localFileStream== null)
                                    {
                                        localFileStream = new FileStream(fnLocal, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                                        localFileStream.Seek(blockInfo.currentPos, SeekOrigin.Begin);
                                    }

                                    int dataSizeUpcompressed = bbi.dataSize;
                                    if (bbi.dataCompressed)
                                    {
                                        Byte[] blockToSave = RemoteObject.MyUtility.DeCompression(bbi.buffer, bbi.bufferStartPos, bbi.dataSize);
                                        localFileStream.Write(blockToSave, 0, blockToSave.Length);
                                        dataSizeUpcompressed = blockToSave.Length;
                                    }
                                    else
                                    {
                                        //The final file write --------------------------------------------------
                                        localFileStream.Write(bbi.buffer, bbi.bufferStartPos, bbi.dataSize);
                                        //The final file write --------------------------------------------------
                                    }

                                    lock (ownerJob)
                                    {
                                        //after save successful, then update the block info. This is important for broken-resuming
                                        blockInfo.currentPos += dataSizeUpcompressed;
                                        blockInfo.remainDataLength -= dataSizeUpcompressed;
                                        //update speed data
                                        transferredDataLength += bbi.dataSize;
                                        ownerJob.transferredDataLength += bbi.dataSize;
                                    }
                                }
                                catch
                                {
                                    accessFileError = true;
                                }
                            }
                            Common.releaseBlock(bbi);
                        }
                    }
                    ownerJob.UpdateProgressFile();
                }
            } while (true);
        }

        public RemoteObject.RemoteObjectDownloader GetRemoteObject()
        {
            return (RemoteObject.RemoteObjectDownloader)Activator.GetObject(typeof(RemoteObject.RemoteObjectDownloader), serverUrl);
        }

        public override void Run()
        {
            base.Run();

            int startPos = blockInfo.currentPos;
            int endPos = blockInfo.currentPos + blockInfo.remainDataLength;

            int dataLength = blockInfo.remainDataLength;

            //set transfer param
            param.BlockSize = Common.BlockSize;
            param.UseCompressMode = ownerJob.useCompressionMode;

            String debugInfo = startPos.ToString("D10") + "-" + (startPos + dataLength).ToString("D10") + " From " + serverUrl.Substring(6, 20);
            try
            {
                Console.WriteLine(debugInfo);

                RemoteObject.RemoteObjectDownloader proxyObjectTcp = null;
                long clientID = 0;

                //try to init file transfer
                try
                {
                    proxyObjectTcp = proxyObjectTcpReuse != null ? proxyObjectTcpReuse : GetRemoteObject();
                    clientID = proxyObjectTcp.GetNewClientID();
                    proxyObjectTcp.StartDownloadFile(fnRemote, clientID, startPos, dataLength,param);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Network problem ..... ");
                    Console.WriteLine(e);
                    proxyObjectTcp = null;
                }

                proxyObjectTcpReuse = null;

                if (proxyObjectTcp != null)
                {
                    // start the file access thread
                    Thread thread = new Thread(new ThreadStart(FileAccessProc));
                    thread.Start();

                    try
                    {
                        while (!accessFileError)
                        {
                            Boolean dataCompressed = false;
                            Byte[] receiveData = proxyObjectTcp.RecevingFileData(clientID, out dataCompressed);
                            if (receiveData == null || receiveData.Length == 0 || stopFlag || receiveData.Length > param.BlockSize)
                                break;

                            Common.BlockBufferInfo bbi = Common.getFreeBlockOnWait(receiveData.Length);
                            Buffer.BlockCopy(receiveData, 0, bbi.buffer, bbi.bufferStartPos, bbi.dataSize);
                            bbi.dataCompressed = dataCompressed;

                            lock (this)
                            {
                                blocksReceived.Add(bbi);
                            }
                        }
                        proxyObjectTcp.StopDownloadFile(clientID);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(startPos + " --- " + (startPos + dataLength) + " ---- error1 --- ");
                        Console.WriteLine(e);
                    }
                    finally
                    {
                        proxyObjectTcp = null;
                    }
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(startPos + " --- " + (startPos + dataLength) + " ---- error2 --- ");
                Console.WriteLine(e);
            }
            finally
            {
                TimeSpan ts = DateTime.Now - startTime;
                Console.WriteLine(debugInfo + "-done- " + ts.TotalMilliseconds.ToString("f1") + " FreeBlocks : " + Common.getFreeBlockNum() + " Speed: " + this.GetTransferSpeed().ToString("f1") + "KB/s");
                lock (this)
                {
                    blocksReceived.Add(Common.endingBlockBuffer);
                }
                WaitFileAccessProcToStop();
            }
        }

    }
}
